/* 
 * File:   main.c
 * Author: daniel
 *
 * Created on 22 de Maio de 2010, 14:52
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "libMacros.h"

struct macro {
    char nome[255];
    int contExpancao;
    int contParametros;
    char corpo[512];
    char listaParametros[20];
} TabMacros[50];

int countMacros = 0;
int debug = FALSE;

/*
 * Responsável pela abertura do arquivo.
 * Caso o arquivo exista retorna um ponteiro para o mesmo.
 */
FILE * openFile() {

    char strArq[255];
    FILE * arquivo;
    printf("Informe o arquivo *.txt com o código fonte a ser processado: ");
    scanf("%s", strArq);
    getchar();

    if (debug == TRUE)
        printf("[Info]: Abrindo arquivo...\n");

    trataCaminhoArquivo(strArq);

    if ((arquivo = fopen(strArq, "r")) == NULL) {
        perror("Falha ao abrir arquivo");
        exit(EXIT_FAILURE);
    }

    if (debug == TRUE)
        printf("[Info]: Arquivo aberto com sucesso!\n");

    return arquivo;
}

/*
 * Converte todos os caracteres da string passada para UPERCASE
 */
char * strToUpper(char * str) {
    int i;
    for (i = 0; str[i]; i++) {
        str[i] = toupper(str[i]);
    }
    return str;
}

char * removeChar(char * texto, char caractere) {
    char * temp = malloc(sizeof (char) * strlen(texto));
    int i = 0, tam = strlen(texto), a = 0;

    while (i < tam) {
        if (texto[i] != '\n') {
            if (texto[i] != caractere) {
                temp[a++] = texto[i];
            }
        }
        i++;
    }
    return temp;
}

int buscaMacro(char * token) {
    int i;
    for (i = 0; i < countMacros; i++) {
        if (!strcmp(TabMacros[i].nome, token)) {
            return i;
        }
    }
    return FALSE;
}

char *replace_str(char *str, char *orig, char *rep) {
    char * buffer = malloc(sizeof (char) * (strlen(str) + strlen(rep)));
    strcpy(buffer, str);

    char *p;

    if (!(p = strstr(str, orig))) {
        return str;
    }

    do {
        strncpy(buffer, str, p - str);
        buffer[p - str] = '\0';

        sprintf(buffer + (p - str), "%s%s", rep, p + strlen(orig));
        strcpy(str, buffer);
    } while ((p = strstr(str, orig)));

    return str;
}

int paramPos(const int indiceMacro, const char * parametro) {

    char * parametros = malloc(sizeof (char) * strlen(TabMacros[indiceMacro].listaParametros));
    strcpy(parametros, TabMacros[indiceMacro].listaParametros);

    int n = 0;
    char * token = NULL, * stk3;
    token = strtok_r(parametros, ",", &stk3);

    while (token) {
        n++;
        if (!strcmp(token, parametro))
            break;
        token = strtok_r(NULL, ",", &stk3);
    }

    free(parametros);

    return n;
}

char * trataOperando(char * linha, const int indiceMacro) {

    char * nomeMacro = malloc(sizeof (char) * strlen(TabMacros[indiceMacro].nome));
    strcpy(nomeMacro, TabMacros[indiceMacro].nome);

    char * novaLinha = malloc(sizeof (char) * (strlen(linha) + strlen(nomeMacro) + 5));

    char * token = NULL, *pt2;
    token = strtok_r(linha, " ", &pt2);
    int instrucaoGravada = FALSE;
    tokenCounter = 0;

    while (token) {

        if (strlen(novaLinha) > 0)
            strcat(novaLinha, " ");

        int indiceInstrucao = FALSE;
        token = strToUpper(token);

        indiceInstrucao = buscaInstrucao(token);

        if (indiceInstrucao != FALSE) {
            strcat(novaLinha, token);
            instrucaoGravada = TRUE;
        } else if (token[0] >= '0' && token[0] <= '9') {
            strcat(novaLinha, token);
        } else {
            if (tokenCounter >= 1 && instrucaoGravada == FALSE) {

            } else {
                if (tokenCounter == 0) {
                    //trata label
                    strcat(novaLinha, "L_");
                    strcat(novaLinha, token);
                    strcat(novaLinha, "_");
                    strcat(novaLinha, nomeMacro);
                    strcat(novaLinha, "_*");

                } else {
                    //tratar operando
                    int pos = paramPos(indiceMacro, token);
                    if (pos == 0) {
                        //erro
                    } else {
                        char buffer[255] = {'\0'};
                        sprintf(buffer, "%d", pos);
                        strcat(novaLinha, "#");
                        strcat(novaLinha, buffer);
                    }
                }
            }
        }
        token = strtok_r(NULL, " ", &pt2);
        tokenCounter++;
    }

    free(nomeMacro);

    return novaLinha;
}

char * trataCorpo(const int indiceMacro, const char * parametros) {

    char * novoCorpo = malloc(sizeof (char) * strlen(TabMacros[indiceMacro].corpo));
    strcpy(novoCorpo, TabMacros[indiceMacro].corpo);

    char buffer[255] = {'\0'};
    sprintf(buffer, "%d", TabMacros[indiceMacro].contExpancao);

    novoCorpo = replace_str(novoCorpo, "*", buffer);

    char * parametrosCopy = malloc(sizeof (char) * strlen(parametros));
    strcpy(parametrosCopy, parametros);

    char * parametro = NULL, * pt;
    parametro = strtok_r(parametrosCopy, ",", &pt);
    int n = 0;
    while (parametro) {
        n++;
        sprintf(buffer, "#%d", n);
        novoCorpo = replace_str(novoCorpo, buffer, parametro);
        parametro = strtok_r(NULL, ",", &pt);
    }

    return novoCorpo;

}

void readme() {
    printf("\33[H\33[2J");
    printf("* PC-1 : Processador de Macros\n*\n");
    printf("* O processador de macros irá analisar o código fonte processando e expandindo\n");
    printf("* as macros do código. Recebe um arquivo *.txt com código fonte que é processado\n");
    printf("* e gera um arquivo *.proc.txt que futuramente será utilizado no montador.\n");
    printf("Pressione [ENTER] para continuar...\n");
    getchar();
}

int main() {

    readme();

    FILE * arquivoFonte;
    size_t tamanhoArquivo = 100;
    int encontraDef = FALSE;
    char op;

    printf("\33[H\33[2J");
    printf("\nRodar em modo debug? (s/n): ");

    op = getchar();
    getchar();
    if (op == 's') {
        debug = TRUE;
    }
    printf("Modo debug: ");
    if (debug == TRUE) {
        printf("On\n");
    } else {
        printf("Off\n");
    }

    programCounter = 0;
    sybolCounter = 0;
    fileLine = 0;
    tokenCounter = 0;
    errorCounter = 0;
    numOperandos = 0;
    locSTOP = FALSE;

    if (debug == TRUE)
        printf("[Info]: Inicializando tabela de instruções...\n");

    inicializaTabInstrucoes();

    arquivoFonte = openFile();

    fseek(arquivoFonte, 0, SEEK_SET);

    char * linha = malloc(sizeof (char) * tamanhoArquivo);

    if (debug == TRUE)
        printf("[Info]: Iniciando processo de leitura do arquivo...\n");
    do {
        if (fgets(linha, tamanhoArquivo, arquivoFonte)) {

            char * token = NULL, *pt1;
            token = strtok_r(linha, " ", &pt1);

            while (token) {
                token = strToUpper(token);
                if (!strcmp(token, "MCDEF")) {
                    encontraDef = TRUE;
                    TabMacros[countMacros].contExpancao = 0;
                    token = strtok_r(NULL, " ", &pt1);
                    strcpy(TabMacros[countMacros].nome, token);
                    token = strtok_r(NULL, ",", &pt1);
                    while (token) {
                        char * newTok = removeChar(token, ' ');
                        if (strlen(TabMacros[countMacros].listaParametros) > 0) {
                            strcat(TabMacros[countMacros].listaParametros, ",");
                        }
                        strcat(TabMacros[countMacros].listaParametros, newTok);
                        TabMacros[countMacros].contParametros++;
                        token = strtok_r(NULL, ",", &pt1);
                        free(newTok);
                    }
                    while (fgets(linha, tamanhoArquivo, arquivoFonte)) {
                        char * newLinha = removeChar(linha, '\n');
                        if (!strcmp(newLinha, "MCEND")) {
                            break;
                        } else {
                            newLinha = trataOperando(newLinha, countMacros);
                            strcat(newLinha, "\n");
                            strcat(TabMacros[countMacros].corpo, newLinha);
                        }
                    }
                    countMacros++;
                    encontraDef = FALSE;
                } else {
                    break;
                }

                token = strtok_r(NULL, " ", &pt1);
            }
        }
    } while (!feof(arquivoFonte));

    if (debug == TRUE) {
        printf("[Info]: Processo de leitura do arquivo concluído!\n");
        printf("        [%d] Definições de macros foram encontradas e tratadas!\n", countMacros);
        if (countMacros > 0) {
            printf("        Mostrar macros? (s/n): ");
            op = getchar();
            getchar();
            if (op == 's') {
                printf("\33[H\33[2J");
                printf("[Macros]\n");
                int i;
                for (i = 0; i < countMacros; i++) {
                    printf("Macro %d: %s\n", i + 1, TabMacros[i].nome);
                    printf("Parâmetros: [%d] - {%s}\n", TabMacros[i].contParametros, TabMacros[i].listaParametros);
                    printf("Corpo:\n%s\n", TabMacros[i].corpo);
                    printf("--------------------------------------------------------\n");
                }
                printf("Pressione [ENTER] para continuar...\n");
                getchar();
            }

        }
    }

    FILE * arquivoProc;

    char caminhoAbsolutoProc[255] = {'\0'};

    strcat(caminhoAbsolutoProc, caminhoArquivo);
    strcat(caminhoAbsolutoProc, nomeArquivo);
    strcat(caminhoAbsolutoProc, ".proc.txt");

    if (debug == TRUE) {
        printf("\33[H\33[2J");
        printf("[Info]: Gerando arquivo '%s'...\n", caminhoAbsolutoProc);
    }

    //Criação do arquivo binário
    arquivoProc = fopen(caminhoAbsolutoProc, "w");

    //Verificação do sucesso da criação do arquivo binário
    if (arquivoProc == NULL) {
        perror("[ERRO] Falha ao criar arquivo:");
        exit(EXIT_FAILURE);
    }

    fseek(arquivoFonte, 0, SEEK_SET);

    do {
        if (fgets(linha, tamanhoArquivo, arquivoFonte)) {

            char * linhaAux = malloc(sizeof (char) * strlen(linha));
            strcpy(linhaAux, linha);

            char * listaParametros = malloc(sizeof (char) * strlen(linha));
            int contaParametros = 0;

            char * token = NULL, * pt4;
            token = strtok_r(linha, " ", &pt4);
            int indice = buscaMacro(token);

            token = strToUpper(token);
            if (indice != FALSE) {
                if (debug == TRUE) {
                    printf("[Info]: Expandindo Macro: %s\n", TabMacros[indice].nome);
                }
                token = strtok_r(NULL, ",", &pt4);
                while (token) {
                    char * newTok = removeChar(token, ' ');
                    if (strlen(listaParametros) > 0) {
                        strcat(listaParametros, ",");
                    }
                    strcat(listaParametros, newTok);
                    contaParametros++;
                    token = strtok_r(NULL, ",", &pt4);
                    free(newTok);
                }
                if (contaParametros == TabMacros[indice].contParametros) {
                    fputs(trataCorpo(indice, listaParametros), arquivoProc);
                    TabMacros[indice].contExpancao++;
                } else {
                    printf("[ERRO]: Parâmetros inválidos\n");
                    printf("        Macro: %s\n", TabMacros[indice].nome);
                    printf("        Parâmetros: [%d] - {%s}\n", TabMacros[indice].contParametros, TabMacros[indice].listaParametros);
                    printf("        Parâmetros passados: [%d] - {%s}\n", contaParametros, listaParametros);
                    printf("Deseja continuar? (s/n): ");
                    op = getchar();
                    getchar();
                    if (op != 's') {
                        exit(EXIT_FAILURE);
                    }
                }
            } else {
                if (!strcmp(token, "MCDEF")) {
                    while (fgets(linha, tamanhoArquivo, arquivoFonte)) {
                        if (!strcmp(linha, "MCEND\n") || !strcmp(linha, "MCEND")) {
                            break;
                        }
                    }
                } else {
                    fputs(linhaAux, arquivoProc);
                }
            }
            free(linhaAux);
        }
    } while (!feof(arquivoFonte));

    if (linha) {
        free(linha);
    }

    printf("\33[H\33[2J");
    printf("* PC-1 : Processador de Macros\n*\n");
    printf("Processo concluído\n");
    printf("Arquivo gerado: %s\n", caminhoAbsolutoProc);

    fclose(arquivoFonte);
    fclose(arquivoProc);

    return (EXIT_SUCCESS);
}

